 /*******************************************************************************
  * Copyright (c) 2006, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.ide.undo;

 import java.util.ArrayList ;
 import java.util.List ;

 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.SubProgressMonitor;
 import org.eclipse.ui.internal.ide.undo.UndoMessages;

 /**
  * A CopyResourcesOperation represents an undoable operation for copying one or
  * more resources in the workspace. Clients may call the public API from a
  * background thread.
  *
  * This operation can track any overwritten resources and restore them when the
  * copy is undone. It is up to clients to determine whether overwrites are
  * allowed. If a resource should not be overwritten, it should not be included
  * in this operation. In addition to checking for overwrites, the target
  * location for the copy is assumed to have already been validated by the
  * client. It will not be revalidated on undo and redo.
  *
  * This class is intended to be instantiated and used by clients. It is not
  * intended to be subclassed by clients.
  *
  * @since 3.3
  *
  */
 public class CopyResourcesOperation extends
         AbstractCopyOrMoveResourcesOperation {

     IResource[] originalResources;

     ResourceDescription[] snapshotResourceDescriptions;

     /**
      * Create a CopyResourcesOperation that copies a single resource to a new
      * location. The new location includes the name of the copy.
      *
      * @param resource
      * the resource to be copied
      * @param newPath
      * the new workspace-relative path for the copy, including its
      * desired name.
      * @param label
      * the label of the operation
      */
     public CopyResourcesOperation(IResource resource, IPath newPath,
             String label) {
         super(new IResource[] { resource }, new IPath[] { newPath }, label);
         setOriginalResources(new IResource[] { resource });
     }

     /**
      * Create a CopyResourcesOperation that copies all of the specified
      * resources to a single target location. The original resource name will be
      * used when copied to the new location.
      *
      * @param resources
      * the resources to be copied
      * @param destinationPath
      * the workspace-relative destination path for the copied
      * resource.
      * @param label
      * the label of the operation
      */
     public CopyResourcesOperation(IResource[] resources, IPath destinationPath,
             String label) {
         super(resources, destinationPath, label);
         setOriginalResources(this.resources);
     }

     /**
      * Create a CopyResourcesOperation that copies each of the specified
      * resources to its corresponding destination path in the destination path
      * array. The resource name for the target is included in the corresponding
      * destination path.
      *
      * @param resources
      * the resources to be copied. Must not contain null resources.
      * @param destinationPaths
      * a workspace-relative destination path for each copied
      * resource, which includes the name of the resource at the new
      * destination. Must be the same length as the resources array,
      * and may not contain null paths.
      * @param label
      * the label of the operation
      */
     public CopyResourcesOperation(IResource[] resources,
             IPath[] destinationPaths, String label) {
         super(resources, destinationPaths, label);
         setOriginalResources(this.resources);
     }

     /*
      * (non-Javadoc)
      *
      * This implementation copies the resources.
      *
      * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#doExecute(org.eclipse.core.runtime.IProgressMonitor,
      * org.eclipse.core.runtime.IAdaptable)
      */
     protected void doExecute(IProgressMonitor monitor, IAdaptable uiInfo)
             throws CoreException {
         copy(monitor, uiInfo);
     }

     /**
      * Move or copy any known resources according to the destination parameters
      * known by this operation. Store enough information to undo and redo the
      * operation.
      *
      * @param monitor
      * the progress monitor to use for the operation
      * @param uiInfo
      * the IAdaptable (or <code>null</code>) provided by the
      * caller in order to supply UI information for prompting the
      * user if necessary. When this parameter is not
      * <code>null</code>, it contains an adapter for the
      * org.eclipse.swt.widgets.Shell.class
      * @throws CoreException
      * propagates any CoreExceptions thrown from the resources API
      */
     protected void copy(IProgressMonitor monitor, IAdaptable uiInfo)
             throws CoreException {

         monitor.beginTask("", 2000); //$NON-NLS-1$
 monitor
                 .setTaskName(UndoMessages.AbstractResourcesOperation_CopyingResourcesProgress);
         List resourcesAtDestination = new ArrayList ();
         List overwrittenResources = new ArrayList ();

         for (int i = 0; i < resources.length; i++) {
             // Copy the resources and record the overwrites that would
 // be restored if this operation were reversed
 ResourceDescription[] overwrites;
             overwrites = WorkspaceUndoUtil.copy(
                     new IResource[] { resources[i] }, getDestinationPath(
                             resources[i], i), resourcesAtDestination,
                     new SubProgressMonitor(monitor, 1000 / resources.length),
                     uiInfo, true);
             // Accumulate the overwrites into the full list
 for (int j = 0; j < overwrites.length; j++) {
                 overwrittenResources.add(overwrites[j]);
             }
         }

         // Are there any previously overwritten resources to restore now?
 if (resourceDescriptions != null) {
             for (int i = 0; i < resourceDescriptions.length; i++) {
                 if (resourceDescriptions[i] != null) {
                     resourceDescriptions[i]
                             .createResource(new SubProgressMonitor(monitor,
                                     1000 / resourceDescriptions.length));
                 }
             }
         }

         // Reset resource descriptions to the just overwritten resources
 setResourceDescriptions((ResourceDescription[]) overwrittenResources
                 .toArray(new ResourceDescription[overwrittenResources.size()]));

         // Reset the target resources to refer to the resources in their new
 // location.
 setTargetResources((IResource[]) resourcesAtDestination
                 .toArray(new IResource[resourcesAtDestination.size()]));
         monitor.done();
     }

     /*
      * (non-Javadoc)
      *
      * This implementation deletes the previously made copies and restores any
      * resources that were overwritten by the copy.
      *
      * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#doUndo(org.eclipse.core.runtime.IProgressMonitor,
      * org.eclipse.core.runtime.IAdaptable)
      */
     protected void doUndo(IProgressMonitor monitor, IAdaptable uiInfo)
             throws CoreException {
         monitor.beginTask("", 2); //$NON-NLS-1$
 monitor
                 .setTaskName(UndoMessages.AbstractResourcesOperation_CopyingResourcesProgress);
         // undoing a copy is first deleting the copied resources...
 WorkspaceUndoUtil.delete(resources, new SubProgressMonitor(monitor, 1),
                 uiInfo, true);
         // then restoring any overwritten by the previous copy...
 WorkspaceUndoUtil.recreate(resourceDescriptions,
                 new SubProgressMonitor(monitor, 1), uiInfo);
         setResourceDescriptions(new ResourceDescription[0]);
         // then setting the target resources back to the original ones.
 // Note that the destination paths never changed since they
 // are not used during undo.
 setTargetResources(originalResources);
         monitor.done();
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#updateResourceChangeDescriptionFactory(org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory,
      * int)
      */
     protected boolean updateResourceChangeDescriptionFactory(
             IResourceChangeDescriptionFactory factory, int operation) {
         boolean update = false;
         if (operation == UNDO) {
             for (int i = 0; i < resources.length; i++) {
                 update = true;
                 IResource resource = resources[i];
                 factory.delete(resource);
             }
             for (int i = 0; i < resourceDescriptions.length; i++) {
                 update = true;
                 IResource resource = resourceDescriptions[i]
                         .createResourceHandle();
                 factory.create(resource);
             }
         } else {
             for (int i = 0; i < resources.length; i++) {
                 update = true;
                 IResource resource = resources[i];
                 factory.copy(resource, getDestinationPath(resource, i));
             }
         }
         return update;
     }

     /*
      * (non-Javadoc)
      *
      * This implementation computes the ability to delete the original copy and
      * restore any overwritten resources.
      *
      * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#computeUndoableStatus(org.eclipse.core.runtime.IProgressMonitor)
      */
     public IStatus computeUndoableStatus(IProgressMonitor monitor) {
         IStatus status = super.computeUndoableStatus(monitor);
         if (!status.isOK()) {
             return status;
         }
         // If the originals no longer exist, we do not want to attempt to
 // undo the copy which involves deleting the copies. They may be all we
 // have left.
 if (originalResources == null) {
             markInvalid();
             return getErrorStatus(UndoMessages.CopyResourcesOperation_NotAllowedDueToDataLoss);
         }
         for (int i = 0; i < snapshotResourceDescriptions.length; i++) {
             if (!snapshotResourceDescriptions[i].verifyExistence(true)) {
                 markInvalid();
                 return getErrorStatus(UndoMessages.CopyResourcesOperation_NotAllowedDueToDataLoss);
             }
         }
         // undoing a copy means deleting the copy that was made
 if (status.isOK()) {
             status = computeDeleteStatus();
         }
         // and if there were resources overwritten by the copy, can we still
 // recreate them?
 if (status.isOK() && resourceDescriptions != null
                 && resourceDescriptions.length > 0) {
             status = computeCreateStatus(true);
         }

         return status;
     }

     /*
      * Record the original resources, including a resource description to
      * describe it. This is so we can make sure the original resources and their
      * subtrees are intact before allowing a copy to be undone.
      */
     private void setOriginalResources(IResource[] originals) {
         originalResources = originals;
         snapshotResourceDescriptions = new ResourceDescription[originals.length];
         for (int i = 0; i < originals.length; i++) {
             snapshotResourceDescriptions[i] = ResourceDescription
                     .fromResource(originals[i]);
         }
     }
 }

